#!/usr/bin/python
"""
Discussions interface

author: Mikolaj Podlaszewski <mikolaj.podlaszewski@gmail.com>
date: 10 Nov 2009

This module implements interface for discussions as
needed for 'Computational Models of Argument'
course plus some bonuses.
"""


import pyafl as af
import sys
from optparse import OptionParser

#prompt parsing
parser = OptionParser(usage="usage: \n\t%prog <framework description>  \n\t%prog -f <framework file>", version="%prog 0.3")
parser.add_option("-f", "--file", type="string", action="store", dest="aff", default=None,
                  help="file with argumentation framework")
parser.add_option("-m", "--mode", type="choice", choices=['credulous', 'sceptical'], dest="mode", default='sceptical',
                  help="default disscusion mode")
parser.add_option("-C", action="store_const", dest="mode", const='credulous',
                  help="set default disscusion mode to credulous")
parser.add_option("-S", action="store_const", dest="mode", const='sceptical',
                  help="set default disscusion mode to sceptical")
(options, args) = parser.parse_args()


def framework_from_file(filename):
    """Reads argumentation framework from file"""
    try:
        afd = ' '.join([''.join(l.split()) for l in file(filename).readlines()[1:]])
    except IOError:
        print "Error reading file %s" % filename
        raise
    else:
        print "Framework read from file (%s)" % filename
        AF = af.ArgumentationFramework(afd)
        print AF        
        return AF

def framework_from_prompt(afd):
    print "Framework read from prompt (%s)" % afd
    AF = af.ArgumentationFramework(afd)
    print AF
    return AF

#reading framework
if options.aff:
    try:
        AF = framework_from_file(options.aff)
    except Exception:
        AF = af.ArgumentationFramework('')
        print 'Actual framework is empty. Use frame or file command to set framework.'
        print
elif (args):
    AF = framework_from_prompt(' '.join(args))
else:
    parser.print_help()
    print
    exit()

#setting discution mode
dm = options.mode
print "Started in '%s' discussion mode." % dm
print

#fun begins here ...
def read_argument(AF):
    """Reads argument from user"""
    a = None
    while not a:
        an = sys.stdin.readline().strip()
        if not an:
            print "Type your argument please"
        elif not AF.ar.has_key(an):
            print "Argument '%s' is not present in framework.\nPlease type once again." % an
        else:
            a = AF.ar[an]
    return a

#########
# definitions of user commands
# command is the function taking list of arguments as parameter (even when it doesn't need one like quit)
#########

commands = {}  #dictionary with possible commands (command name -> function)

# Technical commands
#####################

#quit
commands['quit'] = commands['q'] = lambda l: quit()
commands['quit'].__doc__ = """Quits program."""

#help
def cmd(l):
    """Prints help for given command. If no command given prints available commands.
    help [command name]
    """
    ac = commands.keys()
    ac.sort()
    if not l:
        print 'Available commands:\n\t %s' % '\n\t '.join(ac)
        print 'Type help [command] to get help on specific command.'
    elif not commands.has_key(l[0]):
        print "Unknown command %s. Type help to list all commands." % l[0]
    elif not hasattr(commands[l[0]], '__doc__'):
        print "No help available for %s." % l[0]
    else:
        print commands[l[0]].__doc__
    print
    return
commands['help'] = cmd

#sceptical, credulous
def cmd(mode):
    if dm == mode:
        print "Already in '%s' discussion mode." % dm
    else:
        dm = mode
        print "Switched to '%s' discussion mode." % dm
    print
commands['sceptical'] = lambda l: cmd('sceptical')
commands['sceptical'].__doc__ = """Switches to sceptical discussion mode."""
commands['credulous'] = lambda l: cmd('credulous')
commands['sceptical'].__doc__ = """Switches to credulous discussion mode."""

#frame
def cmd(l):
    """Creates argumentation framework from given 'chains'

    Chain is a string in which each letter coresponds to one argument
    in the framework. Two consecutive letters expres fact that an argument
    represented by the first one attacks an argument represented by the
    second one. Letters may repeat, no problem. Examples should make it clear.
    
    frame [chain1] [chain2] ...
    frame ab
        creates framework with two arguments a and b in which a attacks b
    frame aab
        creates framework where a attacks itself and b
    frame aab ab
        same as above, repeating existing arguments and edges doesn't metter
    frame aba
        two arguments attacking each other
    """
    global AF
    AF = framework_from_prompt(' '.join(l))
commands['frame'] = cmd 

#file
def cmd(l):
    """Reads argumentation framework from given file.
    file <file name>    
    """
    global AF    
    try:
        AF = framework_from_file(l[0])
    except Exception:
        print 'Reading failed.'
commands['file'] = cmd

#print
def cmd(l):
    """Prints actual argumentation framework."""
    print AF
commands['print'] = cmd

# Labelling command
####################

def compute_labellings(type, function):
   """Returns command computing and printing given type of labellings by given function""" 	
   def cmd(l):
      """Computes %s labellings.""" % type
      labs = function()
      print "\n%d %s labelling(s) found:" % (len(labs), type)
      for l in labs: print l
      print;
   return cmd
      
commands['stable']   = compute_labellings('stable',      AF.find_stable)
commands['semi']     = compute_labellings('semi-stable', AF.find_semi_stable)
commands['prefered'] = compute_labellings('prefered',    AF.find_prefered)
commands['stage']    = compute_labellings('stage',       AF.find_stage)

def cmd(l):
    """Computes grounded labelling."""
    print AF.find_grounded()

commands['grounded'] = cmd

# Discussion commands
#####################

def labs2args(l):
    """Converts labels to arguments, for special label 'all' returns all arguments."""
    try:
       if 'all' in l:
           return AF.ar.values()
       else:
           return [AF.ar[lab] for lab in l]    
    except KeyError(s):
        print "Argument '%s' not present in framework" % s
    return []

#todo: describe it clearly
def argfunc2labfunc(func, single = False):
    """Decorator: takes function that excepts arguments, returns function that takes labels and check if labels are correct."""
    def f(l):        
        try:
           if 'all' in l:
               args = AF.ar.values()
           else:
               args = [AF.ar[lab] for lab in l]    
        except KeyError(s):
            print "Argument '%s' not present in framework." % s
        else:
            if not args:
                print "No arguments given."
                return
            return func(args[0] if single else args)
    f.__doc__ = func.__doc__
    return f

#gquery, pquery, query

def query(args, yes, no, tester):
    """Performs query on argument and prints yes/no message according to result of test."""
    for a in args:
        print (yes if tester(a) else no) % a
    print

def cmd(a):
    """Performs g-discussion for given argument(s)."""
    query(a, "Yes, %s is in grounded extension.", \
             "No, %s is not in grounded extension.", \
             AF.sceptical_acceptance)
commands['gquery'] = argfunc2labfunc(cmd)

def cmd(a):
    """Performs p-discussion for given argument(s)."""
    query(a, "Yes, %s is in some extension.", \
             "No, %s is not in any extension.", \
             AF.credulous_acceptance)
commands['pquery'] = argfunc2labfunc(cmd)

def cmd(l):
    """Performs g-discussion or p-discussion for given arguments according to current discussion mode."""
    if dm == "credulous": commands['pquery'](l)
    else: commands['gquery'](l)
commands['query'] = cmd

#pdiscuss, gdiscuss, discuss

#todo: refactor so discussion is suffitient
def discuss(a, tester, discussion, opponent, proponent, name):
    """Performs general discussion."""

    human_player = lambda d: read_argument(AF)

    try:
        print
        print 'Starting', name, 'for', a
        if tester(a):
            print "I will argue in favor of %s" % a
            discussion.argue(a)
            print 'PROP', a
            print "Please type your argument"
            moves, waits = human_player, proponent
        else:
            print "I will argue against %s" % a
            discussion.argue(a)
            print 'PROP', a
            moves, waits = opponent, human_player
        while True:
          na = moves(discussion)
          print "%s argues %s" % ('OPP' if discussion.turn == af.Discussion.OPPONENT else 'PROP', na)
          discussion.argue(na)
          moves, waits = waits, moves
    except af.IllegalMove, e:
        print 'Lose by illegal move'
    except af.DiscussionFinished, e:
        if discussion.winner == af.Discussion.OPPONENT: print "Opponent won!"
        else: print "Proponent won!"
        print e
        print
        print name, 'Finished.'
        print

def cmd(arg):
    """Interactive g-discussion for given argument."""
    discuss(arg, AF.sceptical_acceptance, \
                 af.gDiscussion(AF), \
                 AF.sceptical_acceptance_opponent, \
                 AF.sceptical_acceptance_proponent, \
                 "g-discussion")
commands['gdiscuss'] = argfunc2labfunc(cmd, single=True)

def cmd(arg):
    """Interactive p-discussion for given argument."""
    discuss(arg, AF.credulous_acceptance, \
                 af.pDiscussion(AF), \
                 AF.credulous_acceptance_opponent, \
                 AF.credulous_acceptance_proponent, \
                 "p-discussion")
commands['pdiscuss'] = argfunc2labfunc(cmd, single=True)

def cmd(l):
    """Interactive g-discussion or p-discussion for given argument according to current discussion mode."""
    if dm == "credulous": commands['pdiscuss'](l)
    else: commands['gdiscuss'](l)
commands['discuss'] = cmd

#status
def cmd(args):
    """Returns status for given argument(s).

    status a    returns status of argument a.
    status a b  returns status of argument a and b.
    status all  returns status of all arguments in the current framework.
    """
    for a in args:
        print "status %s: %s" % (str(a), af.s2s(AF.status(a)))
    print
commands['status'] = argfunc2labfunc(cmd)
commands['question'] = commands['status']

#explain
def cmd(arg):
    """Explains status of an argument.
    explain [argument name]
    """    
    
    status, witnes = AF.statusWithWitness(arg)
    print
    if status == set(['IN']):
        print "1) The proponent has winning strategy for %s in the g-discussion" % arg
        print "\tType 'gdiscuss %s' to check" % arg
    elif status == set(['OUT']):
        print "1) The proponent has winning strategy in the g-discussion for %s that defeats %s" % (witnes[0], arg)
        print "\tType 'gdiscuss %s' to check" % witnes[0]
    elif status == set(['UNDEC']):
        print "1) There exists no p-disscusion for %s that is won by the proponent" % arg
        print "\tType 'pdiscuss %s' to check" % arg
        print "2) For all defeaters of %s there exists no p-discussion that is won by the proponent" % arg
        print " or\n".join(["\tType 'pdiscuss %s'" % a for a in arg.minus]) + " to check"
    elif status == set(['IN', 'UNDEC']):
        print "1) The proponent does not have winning strategy for %s in the g-discussion" % arg
        print "\tType 'gdiscuss %s' to check" % arg
        print "2) There exists p-disscusion for %s that is won by the proponent" % arg
        print "\tType 'pdiscuss %s' to check" % arg
        print "3) For all defeaters of %s there exists no p-discussion that is won by the proponent" % arg
        print " or\n".join(["\tType 'pdiscuss %s'" % a for a in arg.minus]) + " to check"
    elif status == set(['OUT', 'UNDEC']):
        print "1) There exists no p-disscusion for %s that is won by the proponent" % arg
        print "\tType 'pdiscuss %s' to check" % arg
        print "2) There exists p-disscusion that is won by the proponent for %s that defeats %s" \
              % (witnes[0], arg)
        print "\tType 'pdiscuss %s' to check" % witnes[0]
        print "3) For all defeaters of %s the proponent has no winning strategy for g-discussion"  % arg
        print " or\n".join(["\tType 'gdiscuss %s'" % a for a in arg.minus]) + " to check"
    elif status == set(['IN', 'OUT', 'UNDEC']):
        print "1) There exists p-disscusion for %s that is won by the proponent" % arg
        print "\tType 'pdiscuss %s' to check" % arg
        print "2) There exists p-disscusion that is won by the proponent for %s that defeats %s" % (witnes[0], arg)
        print "\tType 'pdiscuss %s' to check" % witnes[0]
    print
commands['explain'] = argfunc2labfunc(cmd, single=True)
commands['discuss'] = commands['explain']

#main command loop
while True:
    ln = sys.stdin.readline().split()
    if not ln: continue                #skip empty lines
    try:
        commands[ln[0]](ln[1:])
    except KeyError:
        commands['help'](ln)
